tests = {
  'bhaible': ['test-callback.c', 'test-call.c'],
  'call': [
    'align_mixed.c',
    'align_stdcall.c',
    'bpo_38748.c',
    'callback.c',
    'callback2.c',
    'callback3.c',
    'callback4.c',
    'err_bad_typedef.c',
    'float1.c',
    'float2.c',
    'float3.c',
    'float4.c',
    'float.c',
    'float_va.c',
    'longjmp.c',
    'many2.c',
    'many.c',
    'many_double.c',
    'many_mixed.c',
    'negint.c',
    'offsets.c',
    'overread.c',
    'pr1172638.c',
    'promotion.c',
    'pyobjc_tc.c',
    'return_dbl1.c',
    'return_dbl2.c',
    'return_dbl.c',
    'return_fl1.c',
    'return_fl2.c',
    'return_fl3.c',
    'return_fl.c',
    'return_ldl.c',
    'return_ll1.c',
    'return_ll.c',
    'return_sc.c',
    'return_sl.c',
    'return_uc.c',
    'return_ul.c',
    's55.c',
    'strlen2.c',
    'strlen3.c',
    'strlen4.c',
    'strlen.c',
    'struct10.c',
    'struct1.c',
    'struct2.c',
    'struct3.c',
    'struct4.c',
    'struct5.c',
    'struct6.c',
    'struct7.c',
    'struct8.c',
    'struct9.c',
    'struct_by_value_2.c',
    'struct_by_value_3.c',
    'struct_by_value_3f.c',
    'struct_by_value_4.c',
    'struct_by_value_4f.c',
    'struct_by_value_big.c',
    'struct_by_value_small.c',
    'struct_int_float.c',
    'struct_return_2H.c',
    'struct_return_8H.c',
    'uninitialized.c',
    'va_1.c',
    'va_2.c',
    'va_3.c',
    'va_struct1.c',
    'va_struct2.c',
    'va_struct3.c',
    'x32.c',
  ],
  'closures': [
    'closure_fn0.c',
    'closure_fn1.c',
    'closure_fn2.c',
    'closure_fn3.c',
    'closure_fn4.c',
    'closure_fn5.c',
    'closure_fn6.c',
    'closure_loc_fn0.c',
    'closure_simple.c',
    'cls_1_1byte.c',
    'cls_12byte.c',
    'cls_16byte.c',
    'cls_18byte.c',
    'cls_19byte.c',
    'cls_20byte1.c',
    'cls_20byte.c',
    'cls_24byte.c',
    'cls_2byte.c',
    'cls_3_1byte.c',
    'cls_3byte1.c',
    'cls_3byte2.c',
    'cls_3float.c',
    'cls_4_1byte.c',
    'cls_4byte.c',
    'cls_5_1_byte.c',
    'cls_5byte.c',
    'cls_6_1_byte.c',
    'cls_64byte.c',
    'cls_6byte.c',
    'cls_7_1_byte.c',
    'cls_7byte.c',
    'cls_8byte.c',
    'cls_9byte1.c',
    'cls_9byte2.c',
    'cls_align_double.c',
    'cls_align_float.c',
    'cls_align_longdouble.c',
    'cls_align_longdouble_split2.c',
    'cls_align_longdouble_split.c',
    'cls_align_pointer.c',
    'cls_align_sint16.c',
    'cls_align_sint32.c',
    'cls_align_sint64.c',
    'cls_align_uint16.c',
    'cls_align_uint32.c',
    'cls_align_uint64.c',
    'cls_dbls_struct.c',
    'cls_double.c',
    'cls_double_va.c',
    'cls_float.c',
    'cls_longdouble.c',
    'cls_longdouble_va.c',
    'cls_many_mixed_args.c',
    'cls_many_mixed_float_double.c',
    'cls_multi_schar.c',
    'cls_multi_sshort.c',
    'cls_multi_sshortchar.c',
    'cls_multi_uchar.c',
    'cls_multi_ushort.c',
    'cls_multi_ushortchar.c',
    'cls_pointer.c',
    'cls_pointer_stack.c',
    'cls_schar.c',
    'cls_sint.c',
    'cls_sshort.c',
    'cls_struct_va1.c',
    'cls_uchar.c',
    'cls_uint.c',
    'cls_uint_va.c',
    'cls_ulonglong.c',
    'cls_ulong_va.c',
    'cls_ushort.c',
    'err_bad_abi.c',
    'huge_struct.c',
    'nested_struct10.c',
    'nested_struct11.c',
    'nested_struct12.c',
    'nested_struct13.c',
    'nested_struct1.c',
    'nested_struct2.c',
    'nested_struct3.c',
    'nested_struct4.c',
    'nested_struct5.c',
    'nested_struct6.c',
    'nested_struct7.c',
    'nested_struct8.c',
    'nested_struct9.c',
    'nested_struct.c',
    'problem1.c',
    'single_entry_structs1.c',
    'single_entry_structs2.c',
    'single_entry_structs3.c',
    'stret_large2.c',
    'stret_large.c',
    'stret_medium2.c',
    'stret_medium.c',
    'testclosure.c',
    'unwindtest.cc',
    'unwindtest_ffi_call.cc',
  ],
  'complex': [
    'cls_align_complex_double.c',
    'cls_align_complex_float.c',
    'cls_align_complex_longdouble.c',
    'cls_complex_double.c',
    'cls_complex_float.c',
    'cls_complex_longdouble.c',
    'cls_complex_struct_double.c',
    'cls_complex_struct_float.c',
    'cls_complex_struct_longdouble.c',
    'cls_complex_va_double.c',
    'cls_complex_va_float.c',
    'cls_complex_va_longdouble.c',
    'complex_double.c',
    'complex_float.c',
    'complex_int.c',
    'complex_longdouble.c',
    'many_complex_double.c',
    'many_complex_float.c',
    'many_complex_longdouble.c',
    'return_complex1_double.c',
    'return_complex1_float.c',
    'return_complex1_longdouble.c',
    'return_complex2_double.c',
    'return_complex2_float.c',
    'return_complex2_longdouble.c',
    'return_complex_double.c',
    'return_complex_float.c',
    'return_complex_longdouble.c',
  ],
  'go': ['aa-direct.c', 'closure1.c'],
}

feature_check_code = '''
#include <ffi.h>
@0@
/* OK */
#else
#error Failed @0@
#endif
'''

# FIXME: Compiler checks only add -I for source dir and ffi.h is in builddir
# https://github.com/mesonbuild/meson/pull/12157
feature_check_cargs = ['-I' + meson.project_build_root() / 'include']

feature_check = {
  'closures': '#if FFI_CLOSURES',
  'complex': '#ifdef FFI_TARGET_HAS_COMPLEX_TYPE',
  'go': '#if FFI_GO_CLOSURES',
}

add_languages(
  'cpp',
  native: false,
)

# We generate a full set of tests for each optimization
tests_optimizations = get_option('tests_optimizations')
if tests_optimizations == []
  tests_optimizations = is_gnu_like ? ['-O0', '-O2'] : ['']
endif

common_c_args = []
if cc.get_id() == 'clang'
  common_c_args = ['-W', '-Wall']
elif cc.get_id() == 'gcc'
  common_c_args = ['-W', '-Wall', '-Wno-psabi']
endif

# We generate a full set of tests for each ABI
tests_abis = [['']]
if cc.get_id() == 'gcc'
  if cc.compiles(
    feature_check_code.format('#ifdef __i386__'),
    include_directories: ffiinc,
    args: feature_check_cargs,
  )
    tests_abis += [
      ['-DABI_NUM=FFI_STDCALL', '-DABI_ATTR=__STDCALL__'],
      ['-DABI_NUM=FFI_THISCALL', '-DABI_ATTR=__THISCALL__'],
      ['-DABI_NUM=FFI_FASTCALL', '-DABI_ATTR=__FASTCALL__'],
    ]
  elif host_cpu_family == 'x86_64' and \
  cc.compiles(
    feature_check_code.format('#if !defined __ILP32__ && !defined __i386__'),
    include_directories: ffiinc,
    args: feature_check_cargs,
  )
    tests_abis += [['-DABI_NUM=FFI_GNUW64', '-DABI_ATTR=__MSABI__']]
  endif
endif

foreach suite, sources : tests
  # Skip this set of tests if the feature is not supported
  if suite in feature_check
    code = feature_check_code.format(feature_check[suite])
    if not cc.compiles(
      code,
      include_directories: ffiinc,
      args: feature_check_cargs,
    )
      continue
    endif
  endif

  # Add suite specific cflags
  suite_c_args = []
  if is_msvc_like
    # -wd4996  suggest use of vsprintf_s instead of vsprintf
    # -wd4116  unnamed type definition
    # -wd4101  unreferenced local variable
    # -wd4244  implicit conversion to type of smaller size
    # -wd4005  macro redefinition
    # -wd4305  truncation to smaller type
    # -wd4477  printf %lu of uintptr_t
    # -wd4312  implicit conversion to type of greater size
    # -wd4311  pointer truncation to unsigned long
    # -EHsc    C++ Exception Handling (no SEH exceptions)
    if suite == 'bhaible'
      suite_c_args += ['-wd4996', '-wd4116', '-wd4101', '-wd4244']
    elif suite == 'call'
      suite_c_args += [
        '-wd4005',
        '-wd4244',
        '-wd4305',
        '-wd4477',
        '-wd4312',
        '-wd4311',
        '-EHsc',
      ]
    elif suite == 'closure'
      suite_c_args += [
        '-wd4005',
        '-wd4244',
        '-wd4305',
        '-wd4477',
        '-wd4312',
        '-wd4311',
        '-EHsc',
      ]
    endif
  elif is_gnu_like
    if suite == 'bhaible'
      suite_c_args += [
        '-Wno-unused-variable',
        '-Wno-unused-parameter',
        '-Wno-unused-but-set-variable',
        '-Wno-uninitialized',
      ]
    endif
  else
    if suite == 'bhaible'
      suite_c_args += [
        '-Wno-unused-variable',
        '-Wno-unused-parameter',
        '-Wno-uninitialized',
      ]
    endif
  endif

  foreach src : sources
    # No C++ for or1k
    if src.endswith('.cc') and host_cpu_family.startswith('or1k')
      continue
    endif

    base_name = fs.stem(src)
    src = f'libffi.@suite@/@src@'

    # Some tests need to be duplicated for each of those extra cflag
    tests_extra_c_args = []
    if suite == 'bhaible' and base_name == 'test-call'
      foreach i : range(1, 82)
        tests_extra_c_args += f'-DDGTEST=@i@'
      endforeach
    elif suite == 'bhaible' and base_name == 'test-callback'
      code = feature_check_code.format('#if FFI_CLOSURES')
      if not cc.compiles(
        code,
        include_directories: ffiinc,
        args: feature_check_cargs,
      )
        continue
      endif
      foreach i : range(1, 81)
        tests_extra_c_args += f'-DDGTEST=@i@'
      endforeach
    else
      tests_extra_c_args += ''
    endif

    tests_abis_c_args = fs.read(src).contains('ABI_NUM') ? tests_abis : [['']]
    foreach abi_c_args : tests_abis_c_args
      foreach opt_c_arg : tests_optimizations
        foreach extra_c_arg : tests_extra_c_args
          matrix_c_args = []
          matrix_c_args += abi_c_args != [''] ? abi_c_args : []
          matrix_c_args += opt_c_arg != '' ? opt_c_arg : []
          matrix_c_args += extra_c_arg != '' ? extra_c_arg : []
          test_name = base_name
          exe_name = base_name
          if matrix_c_args != []
            test_name += ' ' + ' '.join(matrix_c_args)
            exe_name += ''.join(matrix_c_args).underscorify()
          endif
          exe = executable(
            exe_name,
            src,
            dependencies: ffi_dep,
            c_args: [common_c_args, private_c_args, suite_c_args, matrix_c_args],
            cpp_args: [
              common_c_args,
              private_c_args,
              suite_c_args,
              matrix_c_args,
            ],
            # Not supported by Meson < 0.64, but -Ox in c_args takes precedence
            #override_options: ['optimization=plain'],
          )
          test(
            test_name,
            exe,
            suite: suite,
          )
        endforeach
      endforeach
    endforeach
  endforeach
endforeach
